API Reference

**Referenced Files in This Document** - [worker.js](file://worker.js) - [wrangler.jsonc](file://wrangler.jsonc) - [package.json](file://package.json) - [README.md](file://README.md) - [cloudflare-pages.toml](file://cloudflare-pages.toml) - [src/alliance-login.njk](file://src/alliance-login.njk) - [src/alliance-members.njk](file://src/alliance-members.njk) - [src/feed.njk](file://src/feed.njk) - [src/_data/site.json](file://src/_data/site.json)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion
  10. Appendices

Introduction

This document describes the Cloudflare Workers-based APIs and integrations powering the Invest Australia Alliance member portal, live polling data, CMS authentication, and RSS feed generation. It covers endpoint definitions, request/response schemas, authentication, error handling, rate limiting posture, security considerations, and operational guidance.

Project Structure

The platform is built with:

  • Eleventy static site generation producing the _site directory
  • A Cloudflare Worker that intercepts API and auth routes and serves static assets otherwise
  • Wrangler configuration binding assets and KV namespaces
  • Nunjucks templates for member-facing pages and RSS feed generation
graph TB
Client["Browser / Client Apps"] --> Worker["Cloudflare Worker<br/>worker.js"]
Worker --> |Intercepts| AuthRoutes["Auth Routes<br/>/alliance/login/, /alliance/verify/, /alliance/logout/"]
Worker --> |Intercepts| PollingAPI["Polling API<br/>/api/polling.json"]
Worker --> |Intercepts| CMSAuth["CMS Auth (Legacy)<br/>/api/auth*, OPTIONS"]
Worker --> Static["Static Assets<br/>_site/ via ASSETS binding"]
AuthRoutes --> Resend["Resend Email Service"]
PollingAPI --> Sheets["Google Sheets API"]

Diagram sources

  • [worker.js:297-320](file://worker.js#L297-L320)
  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)

Section sources

  • [worker.js:297-320](file://worker.js#L297-L320)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
  • [package.json:14-13](file://package.json#L14-L13)

Core Components

  • Member authentication API: magic-link login, verification, logout, and session management
  • Live polling data API: reads from Google Sheets and returns standardized JSON
  • CMS integration endpoints: legacy GitHub OAuth for Sveltia CMS
  • RSS feed: Atom XML generated from Eleventy collections

Section sources

  • [worker.js:77-91](file://worker.js#L77-L91)
  • [worker.js:230-276](file://worker.js#L230-L276)
  • [worker.js:180-227](file://worker.js#L180-L227)
  • [src/feed.njk:1-27](file://src/feed.njk#L1-L27)

Architecture Overview

The Worker acts as a router:

  • Auth routes: validate session cookies, issue magic links, verify tokens, and manage logout
  • Polling route: fetches live data from Google Sheets and returns normalized JSON
  • CMS auth routes: legacy GitHub OAuth handshake for Sveltia CMS
  • Static fallback: all other requests are served via the ASSETS binding
sequenceDiagram
participant C as "Client"
participant W as "Worker"
participant KV as "KV Namespaces"
participant R as "Resend"
participant G as "Google Sheets"
rect rgb(255,255,255)
Note over C,W : Member Authentication Flow
C->>W : POST /alliance/login/ {email, _gotcha}
W->>KV : Lookup member : <email>
W->>R : Send magic link email
W-->>C : Redirect /alliance/login/?sent=1
C->>W : GET /alliance/verify/?token=<hex>
W->>KV : Get token : <hex>
W->>KV : Delete token : <hex>
W-->>C : 302 to /alliance/members/ with session cookie
end
rect rgb(255,255,255)
Note over C,W : Polling Data Flow
C->>W : GET /api/polling.json?state=sa|federal
W->>G : Fetch spreadsheet range
G-->>W : Values array
W-->>C : JSON {state, ...polling metrics}
end

Diagram sources

  • [worker.js:97-147](file://worker.js#L97-L147)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [worker.js:233-276](file://worker.js#L233-L276)

Detailed Component Analysis

Member Authentication API

Endpoints

  • POST /alliance/login/

    • Purpose: Issue a one-time magic link email to approved members
    • Method: POST
    • Body: form-encoded fields
      • email: string (required)
      • _gotcha: string (honeypot; bot trap)
    • Response: 302 redirect to /alliance/login/?sent=1 or error query param
    • Security: constant-time email existence checks; always shows “check your email” message
    • Notes: Approved emails are stored in KV under member:
  • GET /alliance/verify/

    • Purpose: Validate magic link token and set session cookie
    • Query: token=
    • Response: 302 to /alliance/members/ with HttpOnly Secure session cookie
    • Security: token is single-use and TTL-bound; deleted after verification
  • GET /alliance/logout/

    • Purpose: Clear session cookie and redirect to login
    • Response: 302 to /alliance/login/ with Max-Age=0 cookie
  • GET /alliance/members/*

    • Purpose: Protected route gate; validates session cookie and serves assets
    • Behavior: If invalid or missing, redirects to /alliance/login/?next=

Session cookie

  • Name: ace_member_session
  • Attributes: HttpOnly, Secure, SameSite=Lax, Max-Age=30 days
  • Signing: HMAC-SHA256 over base64(payload) where payload = email|expiry

Security and validation

  • Input validation: email regex; rejects empty or malformed emails
  • Timing-safe verification: constant-time HMAC comparison
  • CSRF protection: SameSite=Lax; Secure flag mitigates leakage
  • Rate limiting: none implemented in code; rely on external controls

Error handling

  • 400: Invalid request (e.g., missing code in legacy OAuth)
  • 401: Expired or invalid token during verification
  • 500: Internal server errors (e.g., KV unconfigured, network failures)
  • 503: Misconfigured services (e.g., missing KV or API keys)

Example usage

  • Client posts email to /alliance/login/ and waits for email
  • User clicks link to /alliance/verify/?token=...
  • On success, client stores session cookie and accesses /alliance/members/*

Section sources

  • [worker.js:97-147](file://worker.js#L97-L147)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [worker.js:282-295](file://worker.js#L282-L295)
  • [worker.js:81-91](file://worker.js#L81-L91)
  • [worker.js:32-58](file://worker.js#L32-L58)
  • [src/alliance-login.njk:21-34](file://src/alliance-login.njk#L21-L34)
  • [src/alliance-login.njk:65-71](file://src/alliance-login.njk#L65-L71)
  • [src/alliance-members.njk:14](file://src/alliance-members.njk#L14)

Polling Data API

Endpoint

  • GET /api/polling.json
    • Purpose: Return live polling snapshot for South Australia or Federal
    • Query parameters:
      • state: sa or federal (default sa)
    • Response: JSON object with normalized fields
      • state: "sa" or "federal"
      • labor_tpp, liberal_tpp, labor_primary, liberal_primary, greens_primary: numbers
      • sample_size: integer
      • margin_of_error: number
      • last_updated: date string
      • _cached_at: ISO timestamp
    • Headers: Content-Type application/json, Access-Control-Allow-Origin acestrategies.au, Cache-Control public, max-age=300, stale-while-revalidate=3600
    • Errors: 500 with JSON { error, message } if fetch fails; 503 if service misconfigured

Integration notes

  • Back-end fetches from Google Sheets using document ID and API key from secrets
  • Uses a fixed range per state; defaults to SA if state=federal is not provided

Section sources

  • [worker.js:233-276](file://worker.js#L233-L276)
  • [README.md:507-508](file://README.md#L507-L508)

CMS Integration APIs (Legacy)

Endpoints

  • OPTIONS *

    • Purpose: CORS preflight for cross-origin requests from CMS
    • Response: Access-Control-Allow-Origin acestrategies.au, methods GET/POST/OPTIONS, headers Content-Type
  • GET /api/auth

    • Purpose: Start GitHub OAuth flow for legacy CMS
    • Query: none
    • Response: 302 redirect to GitHub authorize endpoint with configured client_id and redirect_uri
  • GET /api/auth/callback

    • Purpose: Receive OAuth code and return token to opener window
    • Query: code (authorization code)
    • Response: HTML page that postsMessage the access token to the opener origin

Security and compatibility

  • These endpoints are legacy and not required for Sveltia CMS
  • CORS is restricted to acestrategies.au origin
  • Token is delivered via postMessage to the opener window

Section sources

  • [worker.js:183-191](file://worker.js#L183-L191)
  • [worker.js:193-198](file://worker.js#L193-L198)
  • [worker.js:200-227](file://worker.js#L200-L227)

RSS Feed Generation

Endpoint

  • GET /feed.xml
    • Purpose: Atom XML feed of news articles
    • Source: Eleventy collections.news
    • Output: Atom feed with entries containing title, link, updated, id, and content

Section sources

  • [src/feed.njk:1-27](file://src/feed.njk#L1-L27)
  • [src/_data/site.json:1-20](file://src/_data/site.json#L1-L20)

Dependency Analysis

  • Worker depends on:
    • ASSETS binding for static file serving
    • KV namespaces MEMBER_EMAILS and MAGIC_TOKENS for auth
    • Secrets: SESSION_SECRET, RESEND_API_KEY, GOOGLE_SHEETS_ID, GOOGLE_SHEETS_API_KEY, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
  • External services:
    • Resend for transactional emails
    • Google Sheets for polling data
    • GitHub OAuth for legacy CMS integration
graph LR
W["worker.js"] --> A["ASSETS binding"]
W --> ME["KV: MEMBER_EMAILS"]
W --> MT["KV: MAGIC_TOKENS"]
W --> RS["Resend API"]
W --> GS["Google Sheets API"]
W --> GH["GitHub OAuth"]

Diagram sources

  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
  • [worker.js:98-147](file://worker.js#L98-L147)
  • [worker.js:233-276](file://worker.js#L233-L276)
  • [worker.js:193-227](file://worker.js#L193-L227)

Section sources

  • [wrangler.jsonc:17-34](file://wrangler.jsonc#L17-L34)
  • [worker.js:98-147](file://worker.js#L98-L147)
  • [worker.js:233-276](file://worker.js#L233-L276)
  • [worker.js:193-227](file://worker.js#L193-L227)

Performance Considerations

  • Polling API caching: public cache of 5 minutes with stale-while-revalidate of 1 hour
  • Static asset delivery: via Cloudflare Workers Assets reduces origin load
  • No explicit rate limiting in code; consider deploying CDN rate limits or Worker-side quotas for sensitive endpoints

[No sources needed since this section provides general guidance]

Troubleshooting Guide

Common issues and resolutions

  • Member auth KV not configured

    • Symptom: 503 response indicating KV namespaces not configured
    • Resolution: Create MEMBER_EMAILS and MAGIC_TOKENS KV namespaces and bind them in wrangler.jsonc
  • Missing or invalid secrets

    • Symptom: 503 for polling; 500 for auth-related failures
    • Resolution: Ensure SESSION_SECRET, RESEND_API_KEY, GOOGLE_SHEETS_ID, GOOGLE_SHEETS_API_KEY are set
  • Verification token expired or invalid

    • Symptom: Redirect to login with error=expired or error=invalid
    • Resolution: Request a new magic link; tokens expire after 15 minutes
  • CORS errors for CMS auth

    • Symptom: Preflight blocked for /api/auth*
    • Resolution: Ensure requests originate from acestrategies.au; verify OPTIONS handling
  • Polling data fetch failure

    • Symptom: 500 with error message
    • Resolution: Verify GOOGLE_SHEETS_ID and GOOGLE_SHEETS_API_KEY; confirm sheet range availability

Debugging techniques

  • Inspect cookies: ace_member_session should be present after successful verification
  • Monitor network tab for redirects and headers
  • Check Worker logs in Cloudflare dashboard for runtime errors

Section sources

  • [worker.js:70-75](file://worker.js#L70-L75)
  • [worker.js:244-246](file://worker.js#L244-L246)
  • [worker.js:158-159](file://worker.js#L158-L159)
  • [worker.js:183-191](file://worker.js#L183-L191)
  • [worker.js:270-275](file://worker.js#L270-L275)

Conclusion

The Worker exposes a concise set of authenticated endpoints for member access, a polling data API backed by Google Sheets, and legacy CMS integration hooks. The design emphasizes simplicity, security through signed sessions and constant-time comparisons, and efficient static delivery via Cloudflare Workers Assets.

[No sources needed since this section summarizes without analyzing specific files]

Appendices

Endpoint Reference Summary

  • POST /alliance/login/

    • Purpose: Send magic link
    • Auth: None
    • Response: 302 redirect
    • Errors: 400, 500, 503
  • GET /alliance/verify/?token=

    • Purpose: Validate token and set session
    • Auth: None
    • Response: 302 to members portal with session cookie
    • Errors: 401, 500
  • GET /alliance/logout/

    • Purpose: Clear session
    • Auth: None
    • Response: 302 to login
    • Errors: None
  • GET /alliance/members/*

    • Purpose: Protected route gate
    • Auth: Cookie required
    • Response: Static asset or redirect
    • Errors: Redirect to login
  • GET /api/polling.json?state=sa|federal

    • Purpose: Live polling snapshot
    • Auth: None
    • Response: JSON
    • Errors: 500, 503
  • OPTIONS *

    • Purpose: CORS preflight
    • Auth: None
    • Response: Headers for acestrategies.au
  • GET /api/auth

    • Purpose: Start GitHub OAuth (legacy)
    • Auth: None
    • Response: 302 redirect
    • Errors: 500
  • GET /api/auth/callback

    • Purpose: OAuth callback (legacy)
    • Auth: None
    • Response: HTML with token posted to opener
    • Errors: 400, 500

Section sources

  • [worker.js:97-147](file://worker.js#L97-L147)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [worker.js:282-295](file://worker.js#L282-L295)
  • [worker.js:81-91](file://worker.js#L81-L91)
  • [worker.js:233-276](file://worker.js#L233-L276)
  • [worker.js:183-191](file://worker.js#L183-L191)
  • [worker.js:193-227](file://worker.js#L193-L227)
  • [README.md:642-652](file://README.md#L642-L652)

Request/Response Schemas

Member login (POST /alliance/login/)

  • Request body (form): email (string), _gotcha (string)
  • Response: 302 redirect

Verification (GET /alliance/verify/)

  • Query: token (string)
  • Response: 302 with Set-Cookie: ace_member_session=...; HttpOnly; Secure; SameSite=Lax; Max-Age=...

Polling data (GET /api/polling.json)

  • Query: state (sa|federal)
  • Response JSON:
    • state: "sa"|"federal"
    • labor_tpp: number
    • liberal_tpp: number
    • labor_primary: number
    • liberal_primary: number
    • greens_primary: number
    • sample_size: integer
    • margin_of_error: number
    • last_updated: string (date)
    • _cached_at: string (ISO timestamp)

CMS OAuth (legacy)

  • GET /api/auth: 302 to GitHub authorize
  • GET /api/auth/callback: HTML with postMessage(token)

RSS feed (GET /feed.xml)

  • Response: Atom XML with entries derived from collections.news

Section sources

  • [worker.js:97-147](file://worker.js#L97-L147)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [worker.js:233-276](file://worker.js#L233-L276)
  • [worker.js:193-227](file://worker.js#L193-L227)
  • [src/feed.njk:1-27](file://src/feed.njk#L1-L27)

Authentication Requirements

  • Member session: HttpOnly Secure cookie named ace_member_session
  • Verification: Single-use token bound to KV with TTL
  • OAuth (legacy): GitHub OAuth with client credentials

Section sources

  • [worker.js:12-14](file://worker.js#L12-L14)
  • [worker.js:32-58](file://worker.js#L32-L58)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [worker.js:193-227](file://worker.js#L193-L227)

Security Considerations

  • CORS: Restricted to acestrategies.au for CMS auth endpoints
  • Input validation: email regex; honeypot field for bot detection
  • Output sanitization: HTML emails are minimal and static
  • Cookie security: HttpOnly, Secure, SameSite=Lax, 30-day Max-Age
  • Timing safety: constant-time HMAC verification
  • Secrets management: stored via Wrangler secrets; never committed

Section sources

  • [worker.js:183-191](file://worker.js#L183-L191)
  • [worker.js:111-113](file://worker.js#L111-L113)
  • [worker.js:164-171](file://worker.js#L164-L171)
  • [worker.js:50-54](file://worker.js#L50-L54)
  • [README.md:503-510](file://README.md#L503-L510)

Rate Limiting and Backwards Compatibility

  • Rate limiting: not implemented in code; consider upstream controls
  • Backwards compatibility: legacy GitHub OAuth endpoints remain for CMS compatibility; primary auth uses session cookies

Section sources

  • [worker.js:193-227](file://worker.js#L193-L227)
  • [cloudflare-pages.toml:1-4](file://cloudflare-pages.toml#L1-L4)

Versioning Strategy

  • The project version is 2.0.0 in package metadata
  • No API versioning headers or URLs are used; semantic versioning applies to site and tooling

Section sources

  • [package.json:3](file://package.json#L3)